/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package NN;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author ollo
 */
public class Network {
    Neuron[][] hidden;
    OutNeuron[] outputs;
    private static ByteBuffer buffer;
    
    public Network(int nInputs, int nOutputs, int[] hiddenLayers) {
        outputs = new OutNeuron[nOutputs];
        
        int nHid = (hiddenLayers != null) ? hiddenLayers.length : 0;
        if (nHid > 0)
            hidden = new Neuron[nHid][];
        
        for (int i = 0; i < nOutputs; i++) {
            outputs[i] = new OutNeuron((nHid == 0) ? nInputs : hiddenLayers[nHid-1], i);            
        }  
                
        for (int i = nHid-1; i >= 0; i--) {            
            hidden[i] = new Neuron[hiddenLayers[i]];            
                hidden[i] = createHLayer(hiddenLayers[i], (i == 0) ? nInputs : hiddenLayers[i-1], (i < nHid-1) ? hidden[i+1] : outputs);             
        }        
        if (buffer == null) 
            createBuffer();
    }
    
    public double[] processInput(double[] inputs) {
        if (hidden != null)
            for (Neuron n : hidden[0]) {
                n.recieveInput(inputs);
            } 
        else for (Neuron n : outputs) {
                n.recieveInput(inputs);
            }
        
        if (hidden != null) 
            for (Neuron[] nl : hidden) {
                for (Neuron n : nl) {
                    n.activate();
                }
            }
        double[] outs = new double[outputs.length];
        for (int i = 0; i < outs.length; i++) {
            outputs[i].activate();
            outs[i] = outputs[i].getSignal();
        }
        return outs;
    }

    public void processInput(double[] inputs, double[] outcome, double learning) {
        processInput(inputs);
        
        for (int i = 0; i < outcome.length; i++) {
            outputs[i].setErr(outcome[i]);
        }

        if (hidden != null) {
            for (int i = hidden.length-1; i >= 0; i--) {
                for (Neuron n : hidden[i]) {
                    n.calcNodeErr();
                }
            }
        }

        for (Neuron n : outputs) {
            n.adjustWeights(learning);
        }

        if (hidden != null) {
            for (Neuron[] nl : hidden) {
                for (Neuron neuron : nl) {
                    neuron.adjustWeights(learning);
                }
            }
        }
    }
    
    private Neuron[] createHLayer(int n, int nInputs, Neuron[] outputs) {
        Neuron[] layer = new Neuron[n];
        for (int i = 0; i < n; i++) {
            layer[i] = new Neuron(nInputs, i, outputs);            
        }
        return layer;
    }
    
    private void createBuffer() {
        int n = getNoWeights()*4;
        buffer = ByteBuffer.allocate(n);
    }
    
    public int getNoWeights() {
        int size = outputs[0].getNWeights() * outputs.length;
        for (Neuron[] neurons : hidden) {
            size += neurons[0].getNWeights() * neurons.length;
        }
        return size;
    }
    
    public byte[] getBinaryGenome() {
        buffer.position(0);
        if (hidden != null) {
            for (Neuron[] nl : hidden) {
                for (Neuron neuron : nl) {
                    neuron.writeBinaryGenome(buffer);
                }
            }
        }
        for (OutNeuron outn : outputs) {
            outn.writeBinaryGenome(buffer);
        }
        
        return buffer.array();
    }
    
    public void readBinaryGenome(byte[] bytes) {
        buffer.position(0);
        buffer.put(bytes);
        buffer.position(0);
        if (hidden != null) {
            for (Neuron[] nl : hidden) {
                for (Neuron neuron : nl) {
                    neuron.readBinaryGenome(buffer);
                }
            }
        }
        for (Neuron n : outputs) {
            n.readBinaryGenome(buffer);
        }
    }
}
